import box/{logger,condit}

# 循环
Has-Cmd-List(){
  # 用法: Has-Cmd-List [命令1] [命令2] [命令3]...
  # 用于集中判断多个命令存在
  # 所有命令均存在则返回 0，反之为 1
  # 不存在的命令将储存在数组变量 Has_Cmd_List 中
  unset -v Has_Cmd_List
  Has_Cmd_List=()
  local num=0
  local list=$#
  for i in $@;do
    if Has-Cmd $i;then
      let num++
    else
      Has_Cmd_List+=($i)
    fi
  done
  if ((num == list));then
    return 0
  else
    return 1
  fi  
}

# 删除指定元素
# 用法: Unset-Array-Name [将移除的元素] [将修改的数组名]
Unset-Array-Name(){
  local arg=$1
  local args=$2
  eval "local argv=(\${$args[@]})"
  for ((i=0;i<${#argv[@]};i++));do
    if [[ ${argv[i]} == $arg ]];then
      eval "unset $args[i]"
      break
    fi
  done
}

# 判断进程存在
Has-Process(){
  local argv=($(awk '!seen[$0]++' <<<$@))
  local wait=0
  local args=${#argv[@]}
  for i in ${argv[@]};do
    # 典型的进程查询方法
    local IF_PID=$(ps -ef | grep "\b${i}\b" | grep -v grep | awk '{print $2}')
    [ -z "$IF_PID" ] && let wait++
  done
  if ((wait == args));then
    return 0
  else
    return 1
  fi
}

# 命令重试
# 用法: Retry-Cmd [命令]
# 执行失败时将重试，超过一定次数时将放弃尝试并退出脚本
Retry-Cmd(){
  local run=5
  local num=0
  until "$@";do
    if ((num >= run));then
      Err "重试次数过多"
      exit
    fi
    let num++
    Warn "重试，第 $num 次"
  done
}

# 查询进程名
Search-Process-Name(){
  for i in "$@";do
    ps -p "$i" -o comm= 2>/dev/null
  done
}

# 读取按键
Read-Keyboard(){
  __local_read_key(){ read -sn$1 key </dev/tty;}
  __local_timeout_read(){ read -sn$1 -t$__task key </dev/tty;}
  __local_throw_read(){ read -st$__task </dev/tty;}
  local IFS key __task=0.0001
  __local_read_key 1
  if [[ $key == $'\e' ]] ; then
      __local_timeout_read 1
      if [[ "$key" == "[" ]] ; then
        __local_timeout_read 2
        __local_throw_read
        case $key in
          # 方向键 上下左右
          A)printf "up";; B)printf "down";;
          C)printf "right";; D)printf "left";;
          # 导航 翻页
          H|1~)printf "home";; F|4~)printf "end";;
          5~)printf "pgup";; 6~)printf "pgdn";;
          # 插入 删除
          2~)printf "insert";; 3~)printf "del";;
          # 功能键 F5 ~ F12
          15)printf "f5";; 17)printf "f6";;
          18)printf "f7";; 19)printf "f8";;
          20)printf "f9";; 21)printf "f10";;
          23)printg "f11";; 24)printf "f12";;
        esac
      elif [[ "$key" == O ]];then
        __local_timeout_read 1
        __local_throw_read
        case $key in
          # 功能键 F1 ~ F4
          P)printf "f1";; Q)printf "f2";;
          R)printf "f3";; S)printf "f4";;
        esac
      else
        # ESC
        printf "esc"
      fi
  # 数字键
  elif [[ $key =~ ^[0-9a-zA-Z]$ ]];then
    printf "$key"
  # 空格
  elif [[ "$key" == ' ' ]];then
    printf "space"
  # 回车
  elif [[ "$key" == '' ]];then
    printf "enter"
  # TAB
  elif [[ "$key" == $'\t' ]];then
    printf "tab"
  # 加/减
  elif [[ "$key" =~ [-+] ]];then
    printf "$key"
  fi
}

# 判断变量类型
Type-Value(){
  if [ -z "${!1}" ];then
    echo "zero"      # 空变量
  elif declare -p "$1" 2>/dev/null | grep -q 'declare -a';then
    echo "array"     # 数组
  elif [[ "${!1}" =~ ^-?[0-9]+(\.[0-9]+)?$ ]];then   
    echo "number"    # 数值
  # elif [[ "${!1}" =~ ^([Vv])?[0-9a-zA-Z_-]+(\.[0-9a-zA-Z_-]+)?$ ]];then
  #   echo "version" # 版本号，这可能与其他类型冲突，将合并至字符串类型
  elif [[ "${!1}" =~ ^https?://[0-9a-zA-Z./?=\&%-_]+$ ]];then
    echo "url"       # 链接
  elif [[ "${!1,,}" =~ ^(fals|tru)e$ ]];then
    echo "boolean"   # 布尔值
  elif [[ "${!1}" =~ ^-{1,2}[0-9a-zA-Z_-]+$ ]];then
    echo "option"    # 选项参数
  elif [[ "${!1}" =~ ^.+$ ]];then
    echo "string"    # 字符串
  else
    echo "unknown"   # 未知类型
  fi
}

# 处理组合参数
Get-Option(){
  function __local_argstype { [[ "$(Type-Value "${ARG_KEY}")" == "option" ]] && unset -f $FUNCNAME && Err "$ARG_OPT_ERR";}
  ARG_OPT_ERR="非法的参数组合"
  if [[ "$1" =~ ^--"${ARG_OPTS#*,}"=(.+)$ || "$1" =~ ^-"${ARG_OPTS%,*}"(.+)$ ]];then
    eval "${ARG_KEY}=\"${BASH_REMATCH[1]}\""
    [ -z "${!ARG_KEY}" ] && Err "$ARG_ERR" && exit
    __local_argstype
    __return__=1
  else
    [[ "$1" =~ ^--"${ARG_OPTS#*,}"(.+)$ ]] && Err "$ARG_OPT_ERR"
    [ -z "$2" ] && Err "$ARG_ERR" && exit
    eval "${ARG_KEY}=\"${2}\""
    __local_argstype
    __return__=2
  fi
}

# 向缓冲区发送控制序列
Write-Tty(){
  printf "$@" >/dev/tty
}

# 从缓冲区读取多个指定格式隔断值
Read-Tty(){
  for i in ${@:2};do
    IFS= read -srd"$1" "$i" </dev/tty
  done
}

# 清理缓冲区多出的数据
Clear-Tty(){
    local __stty_origin="$(stty -g)"
    stty -icanon min 0 time 0
    while IFS= read -st0.0001 -n1 -dM </dev/tty;do :;done
    stty "$__stty_origin"
}


# 获取终端尺寸（避免额外安装 ncurses-utils，但我好像忘了 coreutils 也能获取终端尺寸（stty size）
# 无需考虑适配其他终端环境，此项目仅在 Termux 使用
Get-Xterm-Size(){
  local __get_line __get_width
  Write-Tty "\e[18t"
  Read-Tty ';' __ __get_line
  Read-Tty 't' __get_width
  case ${1:-size} in
    size)echo "$__get_line $__get_width";;
    cols)echo "$__get_width";;
    lines)echo "$__get_line";;
  esac
}

# 获取触摸位置（鼠标点击）
# 目前为实验性功能，不稳定且响应速度慢，不建议在项目中作为交互组件使用
Get-Xterm-Mouse(){
  __local_set_xterm(){ Write-Tty "${__tty_mouse}l";stty cooked echo;Clear-Tty;}
  trap 'unset __size;__local_set_xterm;exit' SIGINT
  stty raw -echo isig >/dev/tty
  [[ "$__size" ]] || __size=($(Get-Xterm-Size))
  local __tty_mouse="\e[?1000;1006;1015" \
        __width=${__size[1]} \
        __line=${__size[0]} \
        __max_size=$[512-1] \
        __max_size_condit=0 \
        __get_directi \
        __get_width \
        __get_line \
        __buffer
      ((__width < __max_size)) || let __max_size_condit++
      ((__line < __max_size)) || let __max_size_condit++
      if ((__max_size_condit));then
        Err "过大的终端尺寸 ${__width}x${__line}"
        __local_set_xterm
        return
      fi
      Write-Tty "${__tty_mouse}h"
      Read-Tty 'M' __buffer
      [[ "$__buffer" =~ ([0-9]+\;[0-9]+\;[0-9]+) ]]
      __buffer="${BASH_REMATCH[1]}"
      __local_set_xterm
      echo $__buffer
}

# 排列
Rank-Word(){
  fmt -w $(Get-Xterm-Size cols)
}

# 拦截管道
Ban-Stdin(){
  [[ ! -t 0 && -z $1 ]] && { Err "该时机不宜使用标准输入";exit 1;}
  [[ ! -t 1 && -z $1 ]] && { Err "该时机不宜使用标准输出";exit 1;}
}